#include "transcoder_core.h"

char* SRC_FILENAME;
int src_w, src_h;
int src_image_size = 0;
enum AVPixelFormat src_pix_fmt;
AVRational CODEC_TIME_BASE;
AVRational STREAM_TIME_BASE;
int TICKS_PER_FRAME;
int HAS_AUDIO = 0;
int DELAY = 0;
List *GOP_BUFFER;
sem_t GOP_BUFFER_MUTEX;

List **FULL_GOP_BUFFER;
sem_t *FULL_GOP_BUFFER_MUTEX;
sem_t *FULL_GOP_NUM;
sem_t COUNT_FULL_GOP_BUFFER_REF_GOP;

int OUTPUT_VIDEO_NUM;
sem_t VIDEO_FRAME_COUNT_MUTEX;
int FULL_GOP_BUFFER_SIZE; 

int REFCOUNT;
int START_PTS = -1;
int DECODE_STEP = 300;
int PTS_DURATION = -1;
const int FPS_MEASURE_DURATION = 1;
int DECODED_FRAMES = 0;
sem_t PERIOD_LIST_MUTEX;
sem_t ALIVED_DEC_THREAD_CNT;

int VIDEO_FRAME_COUNT = 0;

List *PERIOD_LIST = NULL;

int GOP_COUNT;
int gop_size;
EncodeStruct ess[6];
int DEC_THREAD_CNT;
int ENC_THREAD_CNT;

int initialize_decode_data()
{
	AVFormatContext * fmt_ctx = NULL;

	/* open input file, and allocate format context */
	if (avformat_open_input(&fmt_ctx, SRC_FILENAME, NULL, NULL) < 0) {
		fprintf(stdout, "Error 27: Could not open source file %s\n", SRC_FILENAME);
		exit(1);
	}

	/* retrieve stream information */
	if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
		fprintf(stdout, "Error 24: Could not find stream information\n");
		exit(1);
	}

	int video_stream_idx = -1;
	int audio_stream_idx = -1;
	AVStream *video_stream = NULL;
	AVCodecContext *video_dec_ctx = NULL;

	if (open_codec_context(&video_stream_idx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {
		video_stream = fmt_ctx->streams[video_stream_idx];
		video_dec_ctx = video_stream->codec;

		src_w = video_dec_ctx->width;
		src_h = video_dec_ctx->height;
		src_pix_fmt = video_dec_ctx->pix_fmt;
		CODEC_TIME_BASE = video_dec_ctx->time_base;

		TICKS_PER_FRAME = video_dec_ctx->ticks_per_frame;

		STREAM_TIME_BASE = video_stream->time_base;
		DELAY = av_rescale_q_rnd(video_stream->start_time, STREAM_TIME_BASE, (AVRational){1,1000}, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); 
	}

	if (open_codec_context(&audio_stream_idx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) {
		HAS_AUDIO = 1;
	}

	uint8_t *video_dst_data[4] = {NULL};
	int      video_dst_linesize[4];
	int ret = av_image_alloc(video_dst_data, video_dst_linesize,
			video_dec_ctx->width, video_dec_ctx->height, video_dec_ctx->pix_fmt, 1);
	if (ret < 0) {
		fprintf(stdout, "Error 28: Could not allocate raw video buffer\n");
		exit(0);
	}
	av_free(video_dst_data[0]);

	src_image_size = ret;

	/* dump input information to stdout */
	av_dump_format(fmt_ctx, 0, SRC_FILENAME, 0);

	if (!video_stream) {
		fprintf(stdout, "Error 24: Could not find video stream in the input, aborting\n");
		exit(1);
	}

	GOP_BUFFER = new_List();
	sem_init(&GOP_BUFFER_MUTEX, 0, 1);

	FULL_GOP_BUFFER = xmalloc(sizeof(List *) * OUTPUT_VIDEO_NUM);
	FULL_GOP_BUFFER_MUTEX = xmalloc(sizeof(sem_t) * OUTPUT_VIDEO_NUM);
	FULL_GOP_NUM = xmalloc(sizeof(sem_t) * OUTPUT_VIDEO_NUM);

	sem_init(&VIDEO_FRAME_COUNT_MUTEX, 0 , 1);

	int i;
	for (i = 0; i < OUTPUT_VIDEO_NUM; i++) {
		FULL_GOP_BUFFER[i] = new_List();
		sem_init(&FULL_GOP_BUFFER_MUTEX[i], 0, 1);
		sem_init(&FULL_GOP_NUM[i], 0, 0);
	}

	sem_init(&COUNT_FULL_GOP_BUFFER_REF_GOP, 0, FULL_GOP_BUFFER_SIZE);



	if(DEC_THREAD_CNT > 1){
		sem_init(&PERIOD_LIST_MUTEX, 0, 1);
		PERIOD_LIST = new_List();

		AVPacket pkt;
		AVFrame* frame = av_frame_alloc();
		int got_frame = 0;
		int finished = 0;
		long last_pts = 0;
		long pts = 0;
		Period* period = NULL;

		while(av_seek_frame(fmt_ctx, video_stream->index, pts, AVSEEK_FLAG_BACKWARD) >= 0){
			while(av_read_frame(fmt_ctx, &pkt) >= 0){
				if (pkt.stream_index == video_stream_idx) {
					if(avcodec_decode_video2(video_dec_ctx, frame, &got_frame, &pkt) >= 0){
						if(got_frame){
							pts = frame->pkt_pts;

							if(PTS_DURATION == -1){
								PTS_DURATION = frame->pkt_duration;
								START_PTS = frame->pkt_pts;

								period = xmalloc(sizeof(Period));
								period->start = pts;

								last_pts = pts;
								pts += DECODE_STEP * PTS_DURATION;
							}else{
								if(last_pts >= pts){
									finished = 1;
									period->end = LONG_MAX;
									insert_List(new_ListNode(period), PERIOD_LIST);
									break;
								}

								period->end = pts - 1;
								insert_List(new_ListNode(period), PERIOD_LIST);

								period = xmalloc(sizeof(Period));
								period->start = pts;

								last_pts = pts;
								pts += DECODE_STEP * PTS_DURATION;
							}

							// flush cached data
							pkt.data = NULL;
							pkt.size = 0;
							do{
								got_frame = 0;
								avcodec_decode_video2(video_dec_ctx, frame, &got_frame, &pkt);
							}while(got_frame);

							break;
						}
					}
				}
			}
			if(finished) break;
		}
	}

	avcodec_close(video_dec_ctx);
	avformat_close_input(&fmt_ctx);

	signal(SIGALRM, print_average_fps);  
	struct itimerval tick;  
	memset(&tick, 0, sizeof(tick));  

	tick.it_value.tv_sec = FPS_MEASURE_DURATION;  
	tick.it_value.tv_usec = 800 * 1000; 
	tick.it_interval.tv_sec = FPS_MEASURE_DURATION;  
	tick.it_interval.tv_usec = 0;  
	setitimer(ITIMER_REAL, &tick, NULL);  

	return 0;
}

void decode_flush(AVPacket* p_pkt, int video_stream_idx, AVCodecContext* video_dec_ctx, AVFrame* frame, uint8_t** video_dst_data, int* video_dst_linesize, Period* p)
{
	int got_frame = 0;
	int frame_index = 0;

	p_pkt->data = NULL;
	p_pkt->size = 0;

	do {
		got_frame = 0;
		if (p_pkt->stream_index == video_stream_idx) {
			if(avcodec_decode_video2(video_dec_ctx, frame, &got_frame, p_pkt) >= 0){
				if (got_frame) {
					if(p == NULL || (frame->pkt_pts >= p->start && frame->pkt_pts <= p->end)){
						frame_index = av_rescale_q_rnd(frame->pkt_pts - START_PTS,  STREAM_TIME_BASE, CODEC_TIME_BASE, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX) / TICKS_PER_FRAME;
						av_image_copy(video_dst_data, video_dst_linesize,
								(const uint8_t **) (frame->data), frame->linesize,
								src_pix_fmt, src_w, src_h);

						sem_wait(&VIDEO_FRAME_COUNT_MUTEX);
						++VIDEO_FRAME_COUNT;
						++DECODED_FRAMES;
						sem_post(&VIDEO_FRAME_COUNT_MUTEX);

						add_scaled_frame_to_gop_buffer(frame_index, video_dst_data);
					}

				}
			}
		}
	} while (got_frame);

}

int decode_period()
{
	binding_core();

	int i = 0;
	int ret, got_frame;
	AVFormatContext *fmt_ctx = NULL;

	AVCodecContext *video_dec_ctx = NULL;
	AVStream *video_stream = NULL;

	uint8_t *video_dst_data[4] = {NULL};
	int      video_dst_linesize[4];
	int video_dst_bufsize;

	int video_stream_idx = -1;
	AVFrame *frame = NULL;
	AVPacket pkt;

	sem_post(&ALIVED_DEC_THREAD_CNT);

	if (avformat_open_input(&fmt_ctx, SRC_FILENAME, NULL, NULL) < 0) {
		fprintf(stdout, "Error 27: Could not open source file %s\n", SRC_FILENAME);
		exit(1);
	}

	if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
		fprintf(stdout, "Error 24: Could not find stream information\n");
		exit(1);
	}

	if (open_codec_context(&video_stream_idx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {
		video_stream = fmt_ctx->streams[video_stream_idx];
		video_dec_ctx = video_stream->codec;
		ret = av_image_alloc(video_dst_data, video_dst_linesize,
				src_w, src_h, src_pix_fmt, 1);
		if (ret < 0) {
			fprintf(stdout, "Error 30: Could not allocate raw video buffer\n");
			exit(1);
		}
		video_dst_bufsize = ret;
	} else {
		fprintf(stdout, "Error 29: no video data.\n");
		exit(1);
	}

	AVDictionary *param = NULL;

	video_dec_ctx->thread_count = 1;

	if(video_dec_ctx->codec_id == AV_CODEC_ID_H264){
		av_dict_set(&param, "x264-params", "threads=1", AV_DICT_APPEND);
	}
	else if(video_dec_ctx->codec_id == AV_CODEC_ID_H265){
		av_dict_set(&param, "x265-params", "pools=none", AV_DICT_APPEND);
		av_dict_set(&param, "x265-params", "frame-threads=1", AV_DICT_APPEND);
	}

	av_dump_format(fmt_ctx, 0, SRC_FILENAME, 0);

	frame = av_frame_alloc();

	if (!frame) {
		fprintf(stdout, "Error 30: Could not allocate frame\n");
		ret = AVERROR(ENOMEM);
		exit(1);
	}

	av_init_packet(&pkt);
	pkt.data = NULL;
	pkt.size = 0;
	Period* p = NULL;
	int period_finished = 0;
	int frame_index = 0;

	//int checking_freq_of_dec_thread_in_frames = CHECKING_FREQ_OF_DEC_THREAD * gop_size;

	while(has_period()){
		p = get_period();
		period_finished = 0;
		if(av_seek_frame(fmt_ctx, video_stream->index, p->start, AVSEEK_FLAG_BACKWARD) < 0){
			fprintf(stdout, "Error 31: Could not seek to pts %ld\n", p->start);
			exit(1);
		}
		while (av_read_frame(fmt_ctx, &pkt) >= 0) {
			do {
				got_frame = 0;
				if (pkt.stream_index == video_stream_idx) {
					ret = avcodec_decode_video2(video_dec_ctx, frame, &got_frame, &pkt);
					if (ret < 0) {
						fprintf(stdout, "Error 32: decoding video frame (%s)\n", av_err2str(ret));
						return ret;
					}
					if (got_frame) {
						if(frame->pkt_pts >= p->start && frame->pkt_pts <= p->end){ 
							frame_index = av_rescale_q_rnd(frame->pkt_pts - START_PTS,  STREAM_TIME_BASE, CODEC_TIME_BASE, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX) / TICKS_PER_FRAME;
							av_image_copy(video_dst_data, video_dst_linesize,
									(const uint8_t **) (frame->data), frame->linesize,
									src_pix_fmt, src_w, src_h);

							sem_wait(&VIDEO_FRAME_COUNT_MUTEX);
							++VIDEO_FRAME_COUNT;
							++DECODED_FRAMES;
							sem_post(&VIDEO_FRAME_COUNT_MUTEX);

							add_scaled_frame_to_gop_buffer(frame_index, video_dst_data);

							/*if (frame_index % checking_freq_of_dec_thread_in_frames == 0) {*/
								/*if(should_decoding_thread_help_with_encoding_task()) {*/
									/*decoding_thread_help_with_encoding_task();*/
								/*}*/
							/*}*/
						}
						if(frame->pkt_pts > p->end) period_finished = 1;
					}
				}

				pkt.data += ret;
				pkt.size -= ret;
			} while (pkt.size > 0);

			if(period_finished){
				decode_flush(&pkt, video_stream_idx, video_dec_ctx, frame, video_dst_data, video_dst_linesize, p);
				break;
			}
		}
	}

	decode_flush(&pkt, video_stream_idx, video_dec_ctx, frame, video_dst_data, video_dst_linesize, p);


	sem_wait(&ALIVED_DEC_THREAD_CNT);
	int alived_decode_thread_count;
	sem_getvalue(&ALIVED_DEC_THREAD_CNT, &alived_decode_thread_count); 

	// last decode thread
	if(alived_decode_thread_count == 0){
		add_remaining_frame_to_full_gop_buffer();
		for (i = 0; i < ENC_THREAD_CNT; i++) {
			Gop *empty_gop = new_Gop(-1, 0);
			add_gop_to_full_gop_buffers(empty_gop);
		}
		GOP_COUNT = (VIDEO_FRAME_COUNT + gop_size - 1) / gop_size;
	}

    while(!all_finished()) {
        EncodeStruct *es_slowest = slowest_es();
        encode_and_scale_thread(es_slowest);
    }

	avcodec_close(video_dec_ctx);
	avformat_close_input(&fmt_ctx);
	av_frame_free(&frame);
	av_free(video_dst_data[0]);

	return ret < 0;
}

int decode()
{
	binding_core();

	int i;
	int ret, got_frame;
	AVFormatContext *fmt_ctx = NULL;

	AVCodecContext *video_dec_ctx = NULL;
	AVStream *video_stream = NULL;

	uint8_t *video_dst_data[4] = {NULL};
	int      video_dst_linesize[4];
	int video_dst_bufsize;

	int video_stream_idx = -1;
	AVFrame *frame = NULL;
	AVPacket pkt;

	if (avformat_open_input(&fmt_ctx, SRC_FILENAME, NULL, NULL) < 0) {
		fprintf(stdout, "Error 27: Could not open source file %s\n", SRC_FILENAME);
		exit(1);
	}

	if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
		fprintf(stdout, "Error 24: Could not find stream information\n");
		exit(1);
	}

	if (open_codec_context(&video_stream_idx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {
		video_stream = fmt_ctx->streams[video_stream_idx];
		video_dec_ctx = video_stream->codec;

		ret = av_image_alloc(video_dst_data, video_dst_linesize,
				src_w, src_h, src_pix_fmt, 1);
		if (ret < 0) {
			fprintf(stdout, "Error 30: Could not allocate raw video buffer\n");
			exit(1);
		}
		video_dst_bufsize = ret;
	} else {
		fprintf(stdout, "Error 25: no video data.\n");
		exit(1);
	}

	AVDictionary *param = NULL; 

	video_dec_ctx->thread_count = 1;

	if(video_dec_ctx->codec_id == AV_CODEC_ID_H264){
		av_dict_set(&param, "x264-params", "threads=1", AV_DICT_APPEND);
	}
	else if(video_dec_ctx->codec_id == AV_CODEC_ID_H265){
		av_dict_set(&param, "x265-params", "pools=none", AV_DICT_APPEND);
		av_dict_set(&param, "x265-params", "frame-threads=1", AV_DICT_APPEND);
	}

	av_dump_format(fmt_ctx, 0, SRC_FILENAME, 0);

	frame = av_frame_alloc();
	if (!frame) {
		fprintf(stdout, "Error 30: Could not allocate frame\n");
		ret = AVERROR(ENOMEM);
		exit(1);
	}

	av_init_packet(&pkt);
	pkt.data = NULL;
	pkt.size = 0;
	int frame_index;

//	int checking_freq_of_dec_thread_in_frames = CHECKING_FREQ_OF_DEC_THREAD * gop_size;

	while (av_read_frame(fmt_ctx, &pkt) >= 0) {
		do {
			got_frame = 0;
			if (pkt.stream_index == video_stream_idx) {
				ret = avcodec_decode_video2(video_dec_ctx, frame, &got_frame, &pkt);
				if (ret < 0) {
					fprintf(stdout, "Error 32: decoding video frame (%s)\n", av_err2str(ret));
					return ret;
				}
				if (got_frame) {
					if(START_PTS == -1) START_PTS = frame->pkt_pts;
					frame_index = av_rescale_q_rnd(frame->pkt_pts - START_PTS,  STREAM_TIME_BASE, CODEC_TIME_BASE, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX) / TICKS_PER_FRAME;
					sem_wait(&VIDEO_FRAME_COUNT_MUTEX);
					++VIDEO_FRAME_COUNT;
					++DECODED_FRAMES;
					sem_post(&VIDEO_FRAME_COUNT_MUTEX);
					av_image_copy(video_dst_data, video_dst_linesize,
							(const uint8_t **) (frame->data), frame->linesize,
							src_pix_fmt, src_w, src_h);

					add_scaled_frame_to_gop_buffer(frame_index, video_dst_data);

					/*if (frame_index % checking_freq_of_dec_thread_in_frames*/
							/*== 0) {*/
						/*if (should_decoding_thread_help_with_encoding_task()) {*/
							/*decoding_thread_help_with_encoding_task();*/
						/*}*/
					/*}*/

				}
			}

			if (ret < 0)
				break;
			pkt.data += ret;
			pkt.size -= ret;
		} while (pkt.size > 0);
	}


	decode_flush(&pkt, video_stream_idx, video_dec_ctx, frame, video_dst_data, video_dst_linesize, NULL);

	add_remaining_frame_to_full_gop_buffer();
	for (i = 0; i < ENC_THREAD_CNT; i++) {
		Gop *empty_gop = new_Gop(-1, 0);
		add_gop_to_full_gop_buffers(empty_gop);
	}
	GOP_COUNT = (VIDEO_FRAME_COUNT + gop_size - 1) / gop_size;

	/*while(!all_encoding_tasks_finished()) {*/
		/*EncodeStruct *es_slowest = slowest_encoding_task();*/
		/*encode_and_scale_thread(es_slowest);*/
	/*}*/

	avcodec_close(video_dec_ctx);
	avformat_close_input(&fmt_ctx);
	av_frame_free(&frame);
	av_free(video_dst_data[0]);

	return ret < 0;
}// EFFECT: initializing encoding related data

int has_period()
{
	int has = 1;
	sem_wait(&PERIOD_LIST_MUTEX);
	if(is_empty_List(PERIOD_LIST)) has = 0;
	sem_post(&PERIOD_LIST_MUTEX);
	return has;
}

Period* get_period()
{
	sem_wait(&PERIOD_LIST_MUTEX);
	if(is_empty_List(PERIOD_LIST)) return NULL;
	Period* p = (Period*)remove_List(PERIOD_LIST)->elem;
	sem_post(&PERIOD_LIST_MUTEX);
	return p;
}
